﻿
// #define DEBUG_DRAW

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using MathUtil;
using Physics;

namespace Tank
{
    public enum AnimationMode
    {
        FORWARD = 1,
        BACKWARD,
        STOP,
        EXPLODE
    }

    //==========================================================================================================
    // Tank Class
    // Desc: Creates a tank object which has a Turret object as well.
    //==========================================================================================================
    public class TankSprite : CircularSprite
    {
        protected Turret turret;                            // Turret object of tank that does firing

        Vector3 direction;                                  // Direction of base
        Vector3 right;
        protected int health;                               // Life
        protected uint armor;                               // Defense
        protected float rotAngle;                           // Current Rotation angle
        protected float rotSpeed;                           // Speed of rotation
        protected float tankWidth;                          // Width of the tank
        bool removeFromGame;                                // Wether or not to remove sprite from game 
        GameTimer destroyTimer;                                 // Timer for removing sprite from game
        Matrix worldMtx;                                    // Rotation Matrix for the base
        Dictionary<AnimationMode, Animation> animMap;       // Animations that this sprite has
        AnimationMode currAnimMode;
        Frame currFrame;
        ParticleEmitter smokeTrail;                         // Smoke trail particle effects
        ParticleEmitter leftTred;                           // Smoke trail particle effects
        ParticleEmitter rightTred;                          // Smoke trail particle effects
        ExplosionEmitter explosionEmitter;                  // Explosion effects for this tank
		bool justDied;										// Added to fix kills bug after death - JAB

        // Player Stats
        ushort numFired;
        ushort numHits;
        ushort numDeaths;
        ushort numKills;

#if DEBUG_DRAW
        Graphic2D debugCircle;
#endif // DEBUG_DRAW

        

        public TankSprite(Vector2 _pos, float _maxVel, float _maxAccel, Vector2 _direction, float _drag, string _textureID, string _name,
                            float _radius, int _health, uint _armor, float _rotSpeed)
            : base(_pos, _maxVel, _maxAccel, _drag, _textureID, _name, _radius)
        {
            health = _health;
            armor = _armor;
            tankWidth = 20;
            rotAngle = 0;
            rotSpeed = _rotSpeed;
            removeFromGame = false;
            PhysType = PhysicsType.PHYS_DYNAMIC;
            numFired = 0;
            numHits = 0;
            numDeaths = 0;
            numKills = 0;
			justDied = false;
            direction = new Vector3(_direction, 0);

            // Find right vector
            right = new Vector3();
            ComputeRightVector();

            worldMtx = new Matrix();

            // Init the timer object for when tank is destroyed
            destroyTimer = new GameTimer(8);                               // Seconds before being marked for 'removed' 

            // Animations
            animMap = new Dictionary<AnimationMode, Animation>();
            currAnimMode = AnimationMode.BACKWARD;

            // Particle effects
            smokeTrail = new ParticleEmitter(15, Position, new Vector2(), -Direction, 40, 0.0f, .5f, 3.0f, 5,
                                            Color.WhiteSmoke, 0.5f, 0, 1.0f, .1f, 2);
            smokeTrail.SetTexture("dust", new Rectangle(0, 0, 31, 31));

            leftTred = new ParticleEmitter(15, Position, new Vector2(), -Direction, 40, 0.0f, .3f, 3.0f, 5,
                                            Color.WhiteSmoke, 0.7f, 0, 0.5f, .1f, 2);
            leftTred.SetTexture("dust", new Rectangle(0, 0, 31, 31));

            rightTred = new ParticleEmitter(15, Position, new Vector2(), -Direction, 40, 0.0f, .3f, 3.0f, 5,
                                            Color.WhiteSmoke, 0.7f, 0, 0.5f, .1f, 2);
            rightTred.SetTexture("dust", new Rectangle(0, 0, 31, 31));

            explosionEmitter = new ExplosionEmitter(Position);

#if DEBUG_DRAW
            debugCircle = new Graphic2D(_pos, new Vector2(5, 5));
            debugCircle.AddFrame("DebugCircle", "DebugCircle", new Rectangle(0, 0, 5, 5), true);
#endif // DEBUG_DRAW
            
        }

        //==========================================================================================================
        // Update
        // Desc: Update sprite actions and animations
        //==========================================================================================================
        public override void Update(float deltaTime)
        {
            if (health > 0)
            {
                base.Update(deltaTime);               

                // Update animations
                UpdateAnimMode();
                Debug.Assert(animMap.Count() > 0);
                Animation currAnimation = animMap[currAnimMode];
                currFrame = currAnimation.Update(deltaTime);
                turret.Position = Position;
            }

            // Update Tred
            turret.Update(deltaTime);

            // Update particle effects
            smokeTrail.Direction = new Vector2(-direction.X, -direction.Y);
            smokeTrail.Position = Position + (-(new Vector2(direction.X, direction.Y))* 40);
            smokeTrail.Update(deltaTime);
            UpdateParticleTred(deltaTime);

            // If dead, update explosion emitter
            if (health == 0)
            {
                explosionEmitter.Update(deltaTime);
            }

#if DEBUG_DRAW
            debugCircle.Position = new Vector2(BoundSphere.Center.X, BoundSphere.Center.Y);
#endif // DEBUG_DRAW

            // Timers
            destroyTimer.Update(deltaTime);
            if (destroyTimer.TimeOut)
            {
                OnDestroy();
            }
            
        }

         //==========================================================================================================
        // Update
        // Desc: Update this sprite
        //==========================================================================================================
        public void UpdateAnimMode()
        { 
            if(Velocity.Length() < 1)
            {
                currAnimMode = AnimationMode.BACKWARD;
                animMap[currAnimMode].Direction = Animation.PlayDirection.PAUSE;
                leftTred.EmitParticles = false;
                rightTred.EmitParticles = false;        
            }
            else
            {
                currAnimMode = AnimationMode.BACKWARD;
                animMap[currAnimMode].Direction = Animation.PlayDirection.REVERSE;
                leftTred.EmitParticles = true;
                rightTred.EmitParticles = true;           
            }
        }

        //==========================================================================================================
        // ComputeParticleTredPosition
        // Desc: Update this sprite
        //==========================================================================================================
        public void UpdateParticleTred(float deltaTime)
        {
            Vector2 dir = new Vector2(right.X, right.Y);
            leftTred.Position = Position + (-dir * tankWidth);
            rightTred.Position = Position + (dir * tankWidth);

            leftTred.Direction = new Vector2(-direction.X,- direction.Y);
            rightTred.Direction = new Vector2(-direction.X, -direction.Y); 

            leftTred.Update(deltaTime);
            rightTred.Update(deltaTime);
        }

        //==========================================================================================================
        // Draw
        // Desc: Draw the base of the tanke using animations, then the turret
        //==========================================================================================================
        public override void Draw(SpriteBatch spriteBatch)
        {
            // Draw dirt particle trail
            //smokeTrail.Draw(spriteBatch);
            leftTred.Draw(spriteBatch);
            rightTred.Draw(spriteBatch);

            //base.Draw(spriteBatch);
            Texture2D texture = World.Instance.GetTexture(TextureID);
            Color drawColor = Color.White;
            if (health == 0)
                drawColor = Color.DimGray;

            Debug.Assert(currFrame != null);
            Rectangle drawRect = new Rectangle(currFrame.x, currFrame.y, currFrame.width, currFrame.height);
            Vector2 origin = new Vector2(currFrame.width * .5f, currFrame.height*.5f);
            spriteBatch.Draw(texture, Position, drawRect, drawColor, rotAngle,
                             origin, drawScale, SpriteEffects.None, 0.0f);

            // Draw turret
            turret.Draw(spriteBatch);

            // If dead, draw explosion emitter
            if (health == 0)
            {
                explosionEmitter.Draw(spriteBatch);
            }

#if DEBUG_DRAW
            Texture2D debugTexture = World.Instance.GetTexture("DebugCircle");
            spriteBatch.Draw(debugTexture, new Vector2(BoundSphere.Center.X - debugTexture.Width * 0.5f,
                                                        BoundSphere.Center.Y - debugTexture.Height * 0.5f), Color.White);
            spriteBatch.Draw(debugTexture, new Vector2(BoundSphere.Center.X - BoundSphere.Radius - debugTexture.Width *0.5f,
                                                        BoundSphere.Center.Y - debugTexture.Height * 0.5f),
                            Color.White);
            spriteBatch.Draw(debugTexture, new Vector2(BoundSphere.Center.X + BoundSphere.Radius - debugTexture.Width * 0.5f,
                                                        BoundSphere.Center.Y - debugTexture.Height * 0.5f),
                            Color.White);
#endif // DEBUG_DRAW
        }


        //==========================================================================================================
        // AddAnimation
        // Desc: Adds a new animation to the sprite's Animation List
        //==========================================================================================================
        public void AddAnimation( Animation anim, AnimationMode mode )
        {
            animMap[mode] = anim;
        }


        //==========================================================================================================
        // RotateBase
        // Desc: Rotate the base towards the direction vector argument passed in
        //==========================================================================================================
        public void Rotate(Vector2 dir)
        {
            ComputeRightVector();   // Get the new right vector
            dir.Normalize();

            float theta = Vector2.Dot(new Vector2(direction.X, direction.Y), dir);
            if (theta <= 1 + MathLib.ZERO_TOLLERANCE && theta >= 1 - MathLib.ZERO_TOLLERANCE)
            {
                return;
            }

            float rgtTheta = Vector2.Dot(new Vector2(right.X, right.Y), dir);

            if ( (theta >= 0 && rgtTheta >= 0) || (theta < 0 && rgtTheta >= 0) )
            {
                RotateBase(true);
            }
            else if ((theta >= 0 && rgtTheta < 0) || ((theta < 0 && rgtTheta < 0)))
            {
                RotateBase(false);
            }
        }


        //==========================================================================================================
        // RotateBase
        // Desc: Rotate the base
        //==========================================================================================================
        public void RotateBase(bool clockwise)
        {
            if (health <= 0)
                return;

            // Update objects in the sprite
            if (clockwise)
            {
                rotAngle += rotSpeed;
                worldMtx = Matrix.CreateRotationZ(rotSpeed);
            }
            else
            {
                rotAngle -= rotSpeed;
                worldMtx = Matrix.CreateRotationZ(-rotSpeed);
            }
            
            direction = Vector3.Transform(direction, worldMtx);
            direction.Normalize();
        }


        //==========================================================================================================
        // ComputeRightVector
        // Desc: Finds the right vector from a cross product calculation
        //==========================================================================================================
        private void ComputeRightVector()
        {
            Vector3 up = new Vector3(0, 0, 1);
            right = Vector3.Cross(up, direction);
            right.Normalize();
        }


        //==========================================================================================================
        // SetTurret
        // Desc: Sets the turret for the tank from a heap source
        //==========================================================================================================
        public void SetTurret(Turret newTurret)
        {
            turret = newTurret;
        }


        //==========================================================================================================
        // RotateTurret
        // Desc: Rotate the turret
        //==========================================================================================================
        public void RotateTurret(bool clockwise)
        {
            if (health <= 0)
                return;

            //Rotate turret clockwise
            turret.Rotate(clockwise);
        }

        //==========================================================================================================
        // Fire
        // Desc: Tank tells turret to fire
        //==========================================================================================================
        public void Fire()
        {
            bool fired = turret.Fire();
            if (fired)
                ++numFired;
        }

        public void SecondaryFire()
        {
            bool fired = turret.SecondaryFire();
        }

        //==========================================================================================================
        // TakeDamage
        // Desc: Tank takes damage from source
        //==========================================================================================================
        public void ApplyDamage(uint damage)
        {
            // If the tank already died, don't kill it again
			if (health == 0)
			{
				justDied = false;
				return;
			}

            Debug.Assert(damage > armor);
            health -= (int)(damage - armor);

            // Check if the tank is destroyed
            if (health <= 0)
            {
                health = 0;
                Velocity = new Vector2();
                Explode();
				justDied = true;
            }
        }

        private void Explode()
        {
            ++numDeaths;
            destroyTimer.Start();
            explosionEmitter.Position = Position;
        }

        public void IncreaseNumHits()
        {
            ++numHits;
        }

        public void IncreaseNumKills()
        {
            ++numKills;
        }

        public Vector2 Direction
        {
            get { return new Vector2(direction.X, direction.Y); }
        }

        public bool IsDead
        {
            get { return health <= 0; }
        }

		public bool JustDied
		{
			get { return justDied; }
		}

        public bool RemoveFromGame
        {
            get { return removeFromGame; }
        }

        public Projectile GetProjectile(int indx)
        {
            return turret.GetProjectile(indx);
        }

        public int NumProjectiles
        {
            get { return turret.NumProjectiles; }
        }

        //==========================================================================================================
        // OnDestroy
        // Desc: Timer event call back when the sprite should be removed from the game (resources cleaned up)
        //==========================================================================================================
        private void OnDestroy()
        {
            removeFromGame = true;
            destroyTimer.Reset();
        }

        public ushort NumberShotsFired
        {
            get { return numFired; }
        }

        public ushort NumberHits
        {
            get { return numHits; }
        }

        public ushort NumberDeaths
        {
            get { return numDeaths; }
        }

        public ushort NumberKills
        {
            get { return numKills; }
        }

		public int Health
		{
			get { return health; }
		}
    }


    //==========================================================================================================
    // Turret Class
    // Desc: A gun object to be placed on tank
    //==========================================================================================================
    public class Turret : Sprite
    {
        Vector2 direction;                      // Direction the turret is facing in
        Vector2 rotCenter;                      // Center of rotation
        Vector2 firePosition;
        float range;
        float rotAngle;
        float rotVelocity;
        float barrelLen;
        int ammoCount;
        int secondaryAmmoCount = 300;
        int maxAmmoCount;                       // Specify -1 for infinite ammo
        uint baseDamage;
        bool canFirePrimary;
        GameTimer firingTimer;                                 // Timer for removing sprite from game
        
        private Matrix worldMtx;                // Rotation Matrix for the turret
        Frame currFrame;
        AnimationMode currAnimMode;
        Dictionary<AnimationMode, Animation> animMap;       // Animations that this sprite has
        ParticleEmitter cannonShot, secondaryShot;
        
        List<Projectile> projectileList; // TODO: Need projectile class
        TankSprite tankBase;

        public Turret(Vector2 pos, float _maxVel, float _maxAccel, Vector2 _direction, string textureID, string name, 
                        Vector2 _rotCenter, float _range, float _rotAngle, float _rotVelocity,
                        int _maxAmmo, uint _baseDamage, TankSprite tBase)
            : base(pos, _maxVel, _maxAccel, 0, textureID, name)
        {
            direction = _direction;
            rotCenter = _rotCenter;
            firePosition = new Vector2();
            range = _range;
            rotAngle = _rotAngle;
            rotVelocity = _rotVelocity;
            maxAmmoCount = _maxAmmo;
            baseDamage = _baseDamage;
            ammoCount = maxAmmoCount;                               // Specify -1 for unlimited ammo
            worldMtx = new Matrix();
            animMap = new Dictionary<AnimationMode, Animation>();
            currAnimMode = AnimationMode.STOP;
            barrelLen = 65;
            tankBase = tBase;
            canFirePrimary = true;
            firingTimer = new GameTimer(0.5f);                         // Milliseconds before being marked for 'firing' 

            // Particle effects            
            cannonShot = new ParticleEmitter(10, Position, new Vector2(), direction, 50, 0.0f, .9f, 3.0f, 8, Color.WhiteSmoke, 0.5f, 0,
                                                0.8f, 0.1f, 10);
            secondaryShot = new ParticleEmitter(10, Position, new Vector2(), direction, 50, 0.0f, .9f, 3.0f, 8, Color.WhiteSmoke, 0.5f, 0,
                                                0.8f, 0.1f, 10);
            
            cannonShot.SetTexture("dust", new Rectangle(0, 0, 31, 31));
            secondaryShot.SetTexture("PlasmaBlast", new Rectangle(10, 10, 125, 125)); // Projectil particle effect for PLASMA BLAST
            cannonShot.EmitOnce();
            secondaryShot.EmitOnce();

            projectileList = new List<Projectile>();
        }

        public override void Draw(SpriteBatch spriteBatch)
        {
            // Draw projectiles
            foreach (Projectile p in projectileList)
            {
                p.Draw(spriteBatch);
            }

            Color drawColor = Color.White;
            if (tankBase.Health == 0)
                drawColor = Color.DimGray;

            Rectangle sourceRect = new Rectangle(currFrame.x, currFrame.y, currFrame.width, currFrame.height);

            Texture2D texture = World.Instance.GetTexture(TextureID);
            spriteBatch.Draw(texture, Position, sourceRect, drawColor, rotAngle, origin,
                                drawScale, SpriteEffects.None, 0.0f);

            cannonShot.Draw(spriteBatch);
            secondaryShot.Draw(spriteBatch);
        }

        public override void Update(float deltaTime)
        {
            // Update particle systems
            for(int i = 0; i < projectileList.Count; ++i)
            {
                projectileList[i].Update(deltaTime);

                if(projectileList[i].Remove)
                    projectileList.RemoveAt(i);
            }

            // Update Animation frame
            currFrame = animMap[currAnimMode].Update(deltaTime);

            if (!tankBase.IsDead)
            {
                base.Update(deltaTime);
            }

            // Update particle effects
            cannonShot.Position = Position + direction * barrelLen;
            cannonShot.Update(deltaTime);
            cannonShot.Direction = direction;
            cannonShot.Velocity = tankBase.Velocity;
            secondaryShot.Position = Position + direction * barrelLen;
            secondaryShot.Update(deltaTime);
            secondaryShot.Direction = direction;
            secondaryShot.Velocity = tankBase.Velocity;

            // Firing Timer
            firingTimer.Update(deltaTime);
            if (firingTimer.TimeOut)
            {
                OnFirePrimary();
            }
        }

        public void Rotate(bool clockwise)
        {
            //Rotate turret clockwise
            if (clockwise)
            {
                rotAngle += rotVelocity;
                worldMtx = Matrix.CreateRotationZ(rotVelocity);
            }
            else
            {
                rotAngle -= rotVelocity;
                worldMtx = Matrix.CreateRotationZ(-rotVelocity);
            }

            Vector3 newDir = new Vector3();
            newDir = Vector3.Transform(new Vector3(direction, 0), worldMtx);
            direction.X = newDir.X;
            direction.Y = newDir.Y;
        }

        public bool Fire()
        {
            // Infinite ammo is -1, must also be able to fire primary and tank base is not dead
            if ((ammoCount != -1 && ammoCount <= 0) || !canFirePrimary || tankBase.IsDead)
            {
                return false;
            }
            
            firePosition = GetFirePosition();
            Projectile bullet = new Projectile(firePosition, 2000, 2000, 0, "breakdown", "bullet", 1, baseDamage, 5, direction, rotAngle);
            bullet.ApplyAccel(500 * direction);
            projectileList.Add(bullet);

            // Firing Timer
            canFirePrimary = false;
            firingTimer.Start();

            // Create and emmit particle effects for turret blast
            cannonShot.EmitOnce();

            if(ammoCount != -1)
                ammoCount -= 1;

			// Sound: Projectiles
			int num = SoundManager.Instance.Random.Next(0,3);
			SoundType s = num < 1 ? SoundType.TANK_FIRE_1 : (num < 2 ? SoundType.TANK_FIRE_2 : SoundType.TANK_FIRE_3);
			SoundManager.Instance.PlaySound(s, true);
            return true;

        }

        public bool SecondaryFire()
        {
            if (secondaryAmmoCount != -1 && secondaryAmmoCount <= 0)
                return false;

            firePosition = GetFirePosition();
            Projectile blast = new Plasma(firePosition, 2000, 2000, 0, "New-Plasma-Shot", "blast", 5, baseDamage, 5, direction, rotAngle);
            blast.ApplyAccel(100 * direction);
            projectileList.Add(blast);
            secondaryShot.EmitOnce();
            if (secondaryAmmoCount != -1)
                secondaryAmmoCount -= 1;
            //TODO SOUNDS
            return true;
        }

        //==========================================================================================================
        // OnDestroy
        // Desc: Timer event call back when the sprite should be removed from the game (resources cleaned up)
        //==========================================================================================================
        private void OnFirePrimary()
        {
            canFirePrimary = true;
            firingTimer.Reset();
        }

        //==========================================================================================================
        // AddAnimation
        // Desc: Adds a new animation to the sprite's Animation List
        //==========================================================================================================
        public void AddAnimation(Animation anim, AnimationMode mode)
        {
            animMap[mode] = anim;
            currAnimMode = mode;
        }

        public Projectile GetProjectile(int indx)
        {
            return projectileList[indx];
        }

        public int NumProjectiles
        {
            get { return projectileList.Count; }
        }

        Vector2 GetFirePosition()
        {
            Vector2 pos = new Vector2();
            pos = Position + direction * barrelLen;
            return pos;
        }

        public void ReceiveAmmo(int amount)
        {
            ammoCount += amount;
            if (ammoCount > maxAmmoCount)
                ammoCount = maxAmmoCount;
        }

        public int AmmoCount
        {
            get { return ammoCount; }
        }



    }
}
